#include "stdafx.h"
#include "ConsoleLogger.h"
#include "string_util.hpp"

using namespace std;

struct conf_line
{
  enum {
    TYPE_INVALID,
    TYPE_BLANK,
    TYPE_HEADER,
    TYPE_ELEM,
  } type;

  string key;
  string value;
};

bool valid_first(char c)
{
	return isalpha(c);
}

bool valid_descent(char c)
{
	return isalnum(c) || c == '.' || c == '_';
}

bool valid_identifier(const string& s)
{
  if (s.empty()) {
    return false;
  }
  bool first = true;
  for (auto i = s.begin(); i < s.end(); ++i) {
    if (!(first && valid_first(*i)) && !(!first && valid_descent(*i)))
      return false;
    first = false;
  }
  return true;
}

bool is_quote(char c)
{
	return c == '\'' || c == '"';
}

class config
{
public:
  config() {}
  config(const string& file_name)
  {
    load(file_name);
  }

  conf_line parse_line(const string& line)
  {
    conf_line cl;
    cl.type = conf_line::TYPE_INVALID;

    string s = string_util::trim(line);
    if (line.empty()) {
      cl.type = conf_line::TYPE_BLANK;
      return cl;
    }

    if (s.size() < 2) {
      return cl;
    }

    if (s[0] == '[' && s[s.size()-1] == ']')
    {
      cl.key = string_util::trim(s.substr(1, s.size() - 2));
      if (valid_identifier(cl.key)) {
        cl.type = conf_line::TYPE_HEADER;
        return cl;
      }
    }
    else {
      string::size_type pos = s.find_first_of("=");
      if (pos != string::npos) {
        cl.key = string_util::trim(s.substr(0, pos));

        string value = string_util::trim(s.substr(pos + 1));
				if (value.size() >= 2 && is_quote(value[0]) && is_quote(value[value.size() - 1]))
					value = value.substr(1, value.size() - 2);
				cl.value = value;
        cl.type = conf_line::TYPE_ELEM;
      }
    }
    return cl;
  }

  bool load(const string& file_name)
  {
    items_.clear();

    ifstream in(file_name);
		if (!in) {
			LOG_ERROR("read file failed!");
		}

    const int line_size = 2048;
    char in_buf[line_size];
    map<string, map<string, string>>::iterator iter_header;// = items_.end();
    int line_no = 0;
    while (in.getline(in_buf, line_size)) {
      line_no++;
      conf_line cl = parse_line(in_buf);
//			LOG_DEBUG("lineno:" << line_no << ", cl.type:" << cl.type);
      if (cl.type == conf_line::TYPE_BLANK)
        continue;
      if (cl.type == conf_line::TYPE_HEADER) {
				auto& key = cl.key;
        if (items_.find(key) == items_.end()) {
          items_[key] = map<string, string>();
          iter_header = items_.find(key);
          continue;
        }
      } else if (cl.type == conf_line::TYPE_ELEM) {
        if (iter_header != items_.end()) {
          (iter_header->second)[cl.key] = cl.value;
          continue;
        }
      }

      LOG_ERROR("invalid config, lineno:" << line_no);
      return false;
    }

    return true;
  }

	void dump()
	{
		cout << "----begin(" << items_.size() << ")----" << endl;
		for (auto i = items_.begin(); i != items_.end(); ++i) {
			auto & s = i->second;
			cout << i->first << "(" << s.size() << ")" << endl;
			for (auto j = s.begin(); j != s.end(); ++j) {
			 cout << "\t" << j->first << ":\t" << j->second << endl;	
			}
		}
		cout << "----end----" << endl;
	}

private:
  map<string, map<string, string>> items_;
};

